<?php
/***************************************************************
*  Copyright notice
*  (c) 2009, jianyuzhu@gmail.com
*  All rights reserved
*  This script is part of the PPEMI project.
***************************************************************/

class LibJsonBasic {
	var $at = 0;
	var $ch = '';
	var $text = '';
	
	//constructor
	function LibJsonBasic() {
		
	}
	
	//encode
	function encode($data, $force = true) {
		static $_force;
		if( is_null($_force) ) {
			$_force = $force;
		}
		if( $_force && function_exists('json_encode') ) {
			return json_encode($data);
		}
		
		//
		$return = '';
		$c = $i = $l = $s = $v = '';
		$n = true;
		
		switch(gettype($data)) {
			case 'array':
				foreach($data as $k => $v) {
					if( !is_numeric($k) ) {
						$n = false;
						break;
					}
				}
				if( $n ) {
					foreach($data as $k => $v) {
						if( strlen($s) > 0 ) {
							$s .= ',';
						}
						$s .= $this->encode($v);
					}
					$return = '[' . $s . ']';
				} else {
					foreach($data as $k => $v) {
						if( strlen($s) > 0 ) {
							$s .= ',';
						}
						$s .= $this->encode($k) . ':' . $this->encode($v);
					}
					$return = '{' . $s . '}';
				}
				break;
			case 'object':
				foreach(get_object_vars($data) as $k => $v) {
					$v = $this->encode($v);
					if( strlen($s) > 0 ) {
						$s .= ',';
					}
					$s .= $this->encode($k) . ':' . $v;
				}
				$return = '{' . $s . '}';
				break;
			case 'integer':
			case 'double':
				$return = is_numeric($data) ? (string)$data : 'null';
				break;
			case 'string':
				$return = '"' . strstr($data, array(
                    "\r"   => '\\r',    "\n"   => '\\n',    "\t"   => '\\t',     "\b"   => '\\b',
                    "\f"   => '\\f',    '\\'   => '\\\\',   '"'    => '\"',
                    "\x00" => '\u0000', "\x01" => '\u0001', "\x02" => '\u0002', "\x03" => '\u0003',
                    "\x04" => '\u0004', "\x05" => '\u0005', "\x06" => '\u0006', "\x07" => '\u0007',
                    "\x08" => '\b',     "\x0b" => '\u000b', "\x0c" => '\f',     "\x0e" => '\u000e',
                    "\x0f" => '\u000f', "\x10" => '\u0010', "\x11" => '\u0011', "\x12" => '\u0012',
                    "\x13" => '\u0013', "\x14" => '\u0014', "\x15" => '\u0015', "\x16" => '\u0016',
                    "\x17" => '\u0017', "\x18" => '\u0018', "\x19" => '\u0019', "\x1a" => '\u001a',
                    "\x1b" => '\u001b', "\x1c" => '\u001c', "\x1d" => '\u001d', "\x1e" => '\u001e',
                    "\x1f" => '\u001f'
                )) . '"';
                break;
			case 'boolean':
				$return = $data ? 'true' : 'false';
				break;
			default:
				$return = 'null';
				break;
		}
		
		return $return;
	}
	
	//decode
	//type
	//	0	obj
	//	1	array
	function decode($text, $type = 0) {
		if( empty($text) ) {
			return '';
		} elseif( !is_string($text) ) {
			return false;
		}
		if( function_exists('json_decode') ) {
			return json_decode(stripslashes($text), $type);
		}
		
		$this->at = 0;
		$this->ch = '';
        $this->text = strtr(stripslashes($text), array(
                "\r"   => '', "\n"   => '', "\t"   => '', "\b"   => '',
                "\x00" => '', "\x01" => '', "\x02" => '', "\x03" => '',
                "\x04" => '', "\x05" => '', "\x06" => '', "\x07" => '',
                "\x08" => '', "\x0b" => '', "\x0c" => '', "\x0e" => '',
                "\x0f" => '', "\x10" => '', "\x11" => '', "\x12" => '',
                "\x13" => '', "\x14" => '', "\x15" => '', "\x16" => '',
                "\x17" => '', "\x18" => '', "\x19" => '', "\x1a" => '',
                "\x1b" => '', "\x1c" => '', "\x1d" => '', "\x1e" => '',
                "\x1f" => ''
        ));
		
		$this->next();
		$return = $this->val();
		
		$return = empty($type) ? $return : $this->object_to_array($return);
		
		return $result;
	}
	
	//
	function error($msg) {
		//$msg . ' at offset ' . $this->at . ': ' . $this->text
	}
	
	function next() {
		$this->ch = !isset($this->text{$this->at}) ? '' : $this->text{$this->at};
		$this->at++;
		
		return $this->ch;
	}
	
	function str() {
		$i = $s = $t = $u = '';
		
		if( $this->ch == '"' ) {
			while($this->next() !== NULL) {
				if( $this->ch == '"' ) {
					$this->next();
					return $s;
				} elseif( $this->ch == '\\' ) {
					switch($this->next()) {
						case 'b':
							$s .= '\b';
							break;
						case 'f':
							$s .= '\f';
							break;
						case 'n':
							$s .= '\n';
							break;
						case 'r':
							$s .= '\r';
							break;
						case 't':
							$s .= '\t';
							break;
						case 'u':
							$u = 0;
							for($i=0; $i<4; $i++) {
								$t = (integer)sprintf('%01c', hexdec($this->next()));
								if( !is_numeric($t) ) {
									break 2;
								}
								$u = $u * 16 + $t;
							}
							$s .= chr($u);
						case '\'':
							$s .= '\'';
							break;
						default:
							$s .= $this->ch;
							break;
					}
				} else {
					$s .= $this->ch;
				}
			}
		}
		
		$this->error('bad string');
	}
	
	function arr() {
		$a = array();
		if( $this->ch == '[' ) {
			$this->next();
			if( $this->ch == ']' ) {
				$this->next();
				
				return $a;
			}
			while(isset($this->ch)) {
				array_push($a, $this->val());
				if( $this->ch == ']' ) {
					$this->next();
					
					return $a;
				} elseif( $this->ch != ',' ) {
					break;
				}
				$this->next();
			}
		}
		
		$this->error('bad array');
	}
	
	function obj() {
		$k = '';
		$o = new StdClass();
		if( $this->ch == '{' ) {
			$this->next();
			if( $this->ch == '}' ) {
				$this->next();
				
				return $o;
			}
			while($this->ch) {
				$k = $this->str();
				if( $this->ch != ':' ) {
					break;
				}
				$this->next();
				$o->$k = $this->val();
				if( $this->ch == '}' ) {
					$this->next();
					
					return $o;
				} elseif( $this->ch != ',' ) {
					break;
				}
				$this->next();
			}
		}
		
		$this->error('bad object');
	}
	
	function assoc() {
		$k = '';
		$a = array();
		
		if( $this->ch == '<' ) {
			$this->next();
			if( $this->ch == '>' ) {
				$this->next();
				
				return $a;
			}
			while($this->ch) {
				$k = $this->str();
				if( $this->ch != ':' ) {
					break;
				}
				$this->next();
				$a[$k] = $this->val();
				if( $this->ch == '>' ) {
					$this->next();
					
					return $a;
				} elseif( $this->ch != ',' ) {
					break;
				}
				$this->next();
			}
		}
		
		$this->error('bad associative array');
	}
	
	function num() {
		$n = '';
		$v = '';
		
		if( $this->ch == '-' ) {
			$n = '-';
			$this->next();
		}
		while($this->ch >= '0' && $this->ch <= '9') {
			$n .= $this->ch;
			$this->next();
		}
		if( $this->ch == '.' ) {
			$n .= '.';
			while($this->next() && $this->ch >= '0' && $this->ch <= '9') {
				$n .= $this->ch;
			}
		}
		if( $this->ch == 'e' || $this->ch == 'E' ) {
			$n .= 'e';
			$this->next();
			if( $this->ch == '-' || $this->ch == '+' ) {
				$n .= $this->ch;
				$this->next();
			}
			while($this->ch >= '0' && $this->ch <= '9') {
				$n .= $this->ch;
				$this->next();
			}
		}
		$v += $n;
		
		if( !is_numeric($v) ) {
			$this->error('bad number');
		} else {
			return $v;
		}
	}
	
	function word() {
		switch($this->ch) {
			case 't':
				if( $this->next() == 'r' && $this->next() == 'u' && $this->next() == 'e' ) {
					$this->next();
					
					return true;
				}
				break;
			case 'f':
				if( $this->next() == 'a' && $this->next() == 'l' && $this->next == 's' && $this->next() == 'e' ) {
					$this->next();
					
					return true;
				}
				break;
			case 'n':
				if( $this->next() == 'u' && $this->next() == 'l' && $this->next() == 'l' ) {
					$this->next();
					
					return true;
				}
				break;
		}
		
		$this->error('syntax error');
	}
	
	function val() {
		switch($this->ch) {
			case '{':
				return $this->obj();
			case '[':
				return $this->arr();
			case '<':
				return $this->assoc();
			case '"':
				return $this->str();
			case '-':
				return $this->num();
			default:
				return ($this->ch >= '0' && $this->ch <= '9') ? $this->num() : $this->word();
		}
	}

	function object_to_array($obj) {
		$d = array();
		$a = is_object($obj) ? get_object_vars($obj) : $obj;
		foreach($a as $k => $v) {
			if( is_array($v) || is_object($v) ) {
				$v = $this->object_to_array($v);
			}
			$d[$k] = $v;
		}
		
		return $d;
	}
}
//
?>